package org.example.util;

import cn.com.jit.ida.util.pki.encoders.Base64;
import com.alibaba.fastjson.JSONObject;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.ExternalSigningSupport;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSigProperties;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSignDesigner;

import java.io.*;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.*;

public class PDFUtil {
    public static final ThreadLocal<SimpleDateFormat> YYYYMMDDHHMI_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy年MM月dd日 HH时mm分", Locale.CHINESE));

    private static final String SIGNER_NAME = "epolicy";
    private static final String SIGNER_LOCATION = "阳光农业相互保险公司";
    private static final String SIGNATURE_REASON = "电子保单生成";


    /**
     * 内部类，继承SignatureOptions，修改签名数组默认长度
     */
    private static class SignatureOptionsSub extends SignatureOptions {
        public static final int DEFAULT_SIGNATURE_SIZE = 0x4b0;

        public SignatureOptionsSub() {
            super();
        }
    }

    /**
     * 不可见签名
     *
     * @param inPdf 原始PDF文件字节数组（必传）
     * @throws IOException
     */
    public static byte[] signPdf(byte[] inPdf) throws IOException {
        return signPdf(inPdf, false, null, null, null, null, null);
    }

    /**
     * 可见签名
     *
     * @param inPdf 原始PDF文件路径（必传）
     * @param image 包含图片坐标位置的图片对象
     * @return ByteArrayOutputStream
     * @throws IOException
     */
    public static byte[] signPdf(byte[] inPdf, Image image) throws IOException {
        ByteArrayInputStream imageSig = new ByteArrayInputStream(image.getImageByte());
        int indexPage = image.getIndexPage();
        return signPdf(inPdf, true, imageSig, image.getX(), image.getY(), image.getZoom(), indexPage);
    }


    /**
     * @param inPdf     原始PDF文件字节数组（必传）
     * @param isVisible 签名部件是否可见（为false时，后面所有参数都可为null）
     * @param imageSig  签名图片输入流
     * @param x         坐标X
     * @param y         坐标Y
     * @param zoom      缩放比例：-50F
     * @param indexPage 签名在那一页（从1开始）
     * @return ByteArrayOutputStream 签名后的PDF字节流
     * @throws IOException
     */
    public static byte[] signPdf(byte[] inPdf, boolean isVisible, InputStream imageSig, Float x, Float y, Float zoom, Integer indexPage) throws IOException {
        if (inPdf == null || inPdf.length == 0) {
            throw new IllegalArgumentException("inPdf is null or empty");
        }
        PDVisibleSigProperties visibleSignatureProperties;
        PDSignature signature;
        SignatureOptions signatureOptions = null;
        try (ByteArrayOutputStream signPdfData = new ByteArrayOutputStream();
             PDDocument doc = Loader.loadPDF(inPdf)) {
            //设置签名属性
            if (isVisible) {//可见签名
                if (imageSig == null || imageSig.available() <= 0) {
                    throw new IllegalArgumentException("imageSig is null or empty");
                }
                //获取要签名位置的页码
                PDVisibleSignDesigner visibleSignDesigner = getPdVisibleSignDesigner(doc, imageSig, indexPage, x, y, zoom);
                visibleSignatureProperties = getPdVisibleSigProperties(true, visibleSignDesigner, indexPage);
                signature = getPdSignature(visibleSignatureProperties);
                //设置签名样式
                signatureOptions = getSignatureOptions(visibleSignatureProperties);
                //签名可见
                doc.addSignature(signature, signatureOptions);
            } else {
                visibleSignatureProperties = getPdVisibleSigProperties(false, null, null);
                signature = getPdSignature(visibleSignatureProperties);
                //签名不可见
                doc.addSignature(signature);
            }
            //设置输pdf出流
            ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(signPdfData);//这一步处理完之后，整个pdf文档结构就已经全部构建完毕，仅仅缺少签名的信息值没有填充
            //使用外部签名机制（极大加密机生成加密信息）
            byte[] cmsSignature = JITUtil.JitSign(externalSigning.getContent());//使用这正方式获取签名数据流（实际上获取到的数据流是：除了没有添加签名值外，其他所有数据都已经处理完毕了的数据，最后只需要再写如签名值，就和签名后的PDF文件一模一样的数据流）
            externalSigning.setSignature(cmsSignature);

            return signPdfData.toByteArray();
        } finally {
            IOUtils.closeQuietly(signatureOptions);
        }
    }


    /**
     * 设置签名样式
     *
     * @param visibleSignatureProperties 可见的签名属性对象
     * @return
     * @throws IOException
     */
    private static SignatureOptions getSignatureOptions(PDVisibleSigProperties visibleSignatureProperties) throws IOException {
        SignatureOptionsSub signatureOptionsSub = new SignatureOptionsSub();
        signatureOptionsSub.setVisualSignature(visibleSignatureProperties.getVisibleSignature());
        signatureOptionsSub.setPage(visibleSignatureProperties.getPage() - 1);
        return signatureOptionsSub;
    }


    /**
     * 可以附加到文档的数字签名
     *
     * @param visibleSignatureProperties 可见的签名属性对象
     * @return
     */
    private static PDSignature getPdSignature(PDVisibleSigProperties visibleSignatureProperties) {
        PDSignature signature = new PDSignature();
        signature.setFilter(COSName.getPDFName("CFCA.TrustSignPDF"));
        //signature.setFilter(PDSignature.FILTER_CFCA_TRUSTSIGNPDF);//此方法需要修改源代码
        signature.setSubFilter(COSName.getPDFName("cfca.sm2.pkcs7.detached"));
        //signature.setSubFilter(PDSignature.SUBFILTER_CFCA_SM2_PKCS7_DETACHED);//此方法需要修改源代码
        signature.setName(visibleSignatureProperties.getSignerName());
        signature.setLocation(visibleSignatureProperties.getSignerLocation());
        signature.setReason(visibleSignatureProperties.getSignatureReason());
        // the signing date, needed for valid signature
        signature.setSignDate(Calendar.getInstance());
        return signature;
    }


    /**
     * @param doc       pdf文档
     * @param sigImage  签名展示图片
     * @param indexPage 签名展示在那一页（从1开始）
     * @param x         坐标X
     * @param y         坐标Y
     * @param zoom      缩放比例：-50F
     * @return PDVisibleSignDesigner
     * @throws IOException
     */
    private static PDVisibleSignDesigner getPdVisibleSignDesigner(PDDocument doc, InputStream sigImage, Integer indexPage, Float x, Float y, Float zoom) throws IOException {
        PDVisibleSignDesigner visibleSignDesigner = new PDVisibleSignDesigner(doc, sigImage, indexPage);
        if (zoom == null) {
            zoom = -50F;
        }
        visibleSignDesigner.xAxis(x).yAxis(y).zoom(zoom).adjustForRotation();
        return visibleSignDesigner;
    }


    /**
     * 创建可见的签名属性
     *
     * @param isVisible           签名是否可见
     * @param visibleSignDesigner 签名设计属性的类对象
     * @param page                签名展示在那一页（从1开始）
     * @return
     * @throws IOException
     */
    private static PDVisibleSigProperties getPdVisibleSigProperties(boolean isVisible, PDVisibleSignDesigner visibleSignDesigner, Integer page) throws IOException {
        PDVisibleSigProperties visp = new PDVisibleSigProperties();
        visp.signerName(SIGNER_NAME)
                .signerLocation(SIGNER_LOCATION)
                .signatureReason(SIGNATURE_REASON);
        if (isVisible) {
            visp.preferredSize(0)
                    .page(page)
                    .visualSignEnabled(true)
                    .setPdVisibleSignature(visibleSignDesigner);
            // this builds the signature structures in a separate document  将可见的pdf签名样式的流数据进行标准创建后返回，并保存到visibleSignatureProperties 对象中
            visp.buildSignature();//重点在这里，将上面的所有签名图片有关信息进行处理成 InputStream 流的形式，供 SignatureOptions 使用
        }
        return visp;
    }


    /**
     * <br>验证PDF
     * <br>说明：结果字段 flag 值为 0，则验真成功；其他验真失败
     * <br>签名时间字段为：signDate
     *
     * @param filePath 需要验证的签名的PDF文件路径
     * @throws IOException
     */
    public static JSONObject verify(String filePath) throws IOException {
        //校验文件是否存在
        File file = new File(filePath);
        if (!file.exists()) {
            throw new RuntimeException("The PDF verification file does not exist.");
        }
        //验证
        return verify(Files.readAllBytes(file.toPath()));
    }


    /**
     * <br>验证PDF
     * <br>说明：结果字段 flag 值为 0，则验真成功；其他验真失败
     * <br>签名时间字段为：signDate
     *
     * @param pdfInputStream 需要验证的签名的PDF文件输入流
     * @throws IOException
     */
    public static JSONObject verify(InputStream pdfInputStream) throws IOException {
        //校验文件是否存在
        if (pdfInputStream == null) {
            throw new RuntimeException("The PDF verification file data is empty.");
        }
        //验证
        return verify(pdfInputStream.readAllBytes());
    }


    /**
     * <br>验证PDF
     * <br>说明：结果字段 flag 值为 0，则验真成功；其他验真失败
     * <br>签名时间字段为：signDate
     *
     * @param pdfBytes 需要验证的签名的PDF文件字节数据
     */
    public static JSONObject verify(byte[] pdfBytes) {
        JSONObject resultJson = new JSONObject();
        //校验文件是否存在
        if (pdfBytes == null || pdfBytes.length == 0) {
            throw new RuntimeException("The PDF verification file data is empty.");
        }
        //获取签名信息 和 文档信息
        try (PDDocument document = Loader.loadPDF(pdfBytes)) {
            //获取签名数据、原始数据
            PDSignature pdSignature = document.getSignatureDictionaries().get(0);
            byte[] signature = getSignatureData(pdSignature);
            byte[] originPDF = getOriginPDF(pdfBytes, pdSignature);
            //验证PDF
            int flag = JITUtil.JitVerify(originPDF, signature);
            //结果赋值
            resultJson.put("flag", flag);
            Calendar signDate = pdSignature.getSignDate();
            if (signDate != null) {
                resultJson.put("signDate", YYYYMMDDHHMI_FORMAT.get().format(signDate.getTime()));
            }
            ////结果校验
            //if (flag == 0) {
            //    resultJson.put("flag", "0");
            //    resultJson.put("content", "验真成功，此电子单证由阳光农业相互保险公司签发，签发时间为" + "时间");
            //} else {
            //    resultJson.put("flag", "1");
            //    resultJson.put("content", "验真失败，此文件非阳光农业相互保险公司签发或文件已被篡改。");
            //}
            return resultJson;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 通过入参 PDSignature 对象  和  已签名pdf字节数组  获取实际的原始pdf文件内容（不包含签名值）
     *
     * @param pdfBytes    已签名pdf字节数组
     * @param pdSignature 签名对象
     * @return
     */
    public static byte[] getOriginPDF(byte[] pdfBytes, PDSignature pdSignature) {
        //获取原文档数据
        int[] byteRange = pdSignature.getByteRange();
        int len = byteRange[1] + byteRange[3];
        //源文pdf数据（包含签名域信息，不包含签名数据值）
        byte[] originPDF = new byte[len];
        System.arraycopy(pdfBytes, 0, originPDF, 0, byteRange[1]);
        System.arraycopy(pdfBytes, byteRange[2], originPDF, byteRange[1], byteRange[3]);
        return originPDF;
    }

    /**
     * 通过入参 PDSignature 对象获取实际的签名数据内容
     *
     * @param signatureDictionaries 签名对象
     * @return
     */
    public static byte[] getSignatureData(PDSignature signatureDictionaries) {
        //签名数据  获取到的数据直接就可以使用，用来进行数据签名验证
        return Base64.encode(signatureDictionaries.getContents());
    }


    /**
     * PDF插入1张普通图片（非签名图片）
     *
     * @throws IOException
     */
    public static byte[] addImageToPDF(PDDocument document, Image image) throws IOException {
        ArrayList<Image> images = new ArrayList<>();
        images.add(image);
        return addImageToPDF(document, images);
    }


    /**
     * PDF插入多张普通图片（非签名图片）
     *
     * @throws IOException
     */
    public static byte[] addImageToPDF(PDDocument document, ArrayList<Image> images) throws IOException {
        //临时存储pdf流
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
            for (Image image : images) {
                int indexPage = image.getIndexPage() - 1;
                PDPage page = document.getPage(indexPage);//这里计算页码是从0开始
                try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
                    PDImageXObject pdImage = PDImageXObject.createFromByteArray(document, image.getImageByte(), image.getErrMessage());
                    contentStream.drawImage(pdImage, image.getX(), image.getY(), image.getImageWidth(), image.getImageHeight());
                }
            }
            document.save(byteArrayOutputStream);
            document.close();
            //重新解析新的pdf
            return byteArrayOutputStream.toByteArray();
        }
    }


    public static void main(String[] args) throws IOException {
        String OUT_DIR = "target/test-output/";
        //itextVerify(OUT_DIR + "sign_me_1236.pdf");
        //pdfBoxVerify(OUT_DIR + "sign_me_1236.pdf");
        //JitFileVerify("D:\\3334.pdf", "D:\\dsrc.txt");
        verify(OUT_DIR + "sign_me_1236.pdf");


    }


}

